package service

import (
	"context"
	"database/sql"
	"fmt"

	"ctfo-platform-go/cache"
	"ctfo-platform-go/server/model"

	"github.com/redis/go-redis/v9"
	"github.com/uptrace/bun"
)

var (
	errOf = model.ErrOf
)

type UserService interface {
	ListUsers(ctx context.Context, name string) ([]model.DUser, error)
	AddUser(ctx context.Context, user model.UserPayload) error
	PageUsers(ctx context.Context, param model.PageParam, name string) (model.PageResult[model.DUser], error)
	GetUserById(ctx context.Context, id int) (model.DUser, error)
	UpdateUser(ctx context.Context, id int, user model.UserPayload) error
	DeleteUser(ctx context.Context, id int) error
}

type userServiceImpl struct {
	db *bun.DB
}

type userServiceWithCache struct {
	*userServiceImpl
	c cache.CacheRunner
}

func NewUserService(db *bun.DB, rdb *redis.Client) UserService {
	impl := &userServiceImpl{
		db: db,
	}
	return &userServiceWithCache{
		userServiceImpl: impl,
		c:               cache.NewCache(rdb),
	}

}

func (s *userServiceWithCache) DeleteUser(ctx context.Context, id int) error {
	err := s.userServiceImpl.DeleteUser(ctx, id)
	if err != nil {
		return err
	}
	return s.c.Delete(ctx, cacheUserKey(id))
}

func (s *userServiceWithCache) GetUserById(ctx context.Context, id int) (model.DUser, error) {
	var m model.DUser
	err := s.c.Do(ctx, cacheUserKey(id), &m, func() (any, error) {
		return s.userServiceImpl.GetUserById(ctx, id)
	})
	return m, err
}

func (s *userServiceWithCache) UpdateUser(ctx context.Context, id int, param model.UserPayload) error {
	err := s.userServiceImpl.UpdateUser(ctx, id, param)
	if err != nil {
		return err
	}
	return s.c.Delete(ctx, cacheUserKey(id))
}

func cacheUserKey(id int) string {
	return fmt.Sprintf("user:%d", id)
}

// DeleteUser implements UserService.
func (s *userServiceImpl) DeleteUser(ctx context.Context, id int) error {
	return s.db.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
		var user model.DUser
		err := tx.NewSelect().Model(&user).Where("id = ?", id).Limit(1).Scan(ctx)
		if err != nil {
			return err
		}
		_, err = tx.NewDelete().Model(&user).WherePK().Exec(ctx)
		return err
	})
}

// GetUserById implements UserService.
func (s *userServiceImpl) GetUserById(ctx context.Context, id int) (model.DUser, error) {
	var user model.DUser
	err := s.db.NewSelect().Model(&user).Where("id = ?", id).Limit(1).Scan(ctx)
	return user, err
}

// UpdateUser implements UserService.
func (s *userServiceImpl) UpdateUser(ctx context.Context, id int, param model.UserPayload) error {

	return s.db.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
		var user model.DUser
		err := tx.NewSelect().Model(&user).Where("id = ?", id).Limit(1).Scan(ctx)
		if err != nil {
			return err
		}
		user.Name = param.Name
		user.Account = param.Account
		_, err = tx.NewUpdate().Model(&user).WherePK().Exec(ctx)
		return err
	})
}

// PageUsers implements UserService.
func (s *userServiceImpl) PageUsers(ctx context.Context, param model.PageParam, name string) (model.PageResult[model.DUser], error) {
	var users []model.DUser
	qb := s.db.NewSelect().Model((*model.DUser)(nil)).QueryBuilder()
	if name != "" {
		qb.Where("name = ?", name)
	}

	count, err := qb.Unwrap().(*bun.SelectQuery).Offset(param.Offset()).Limit(param.Limit()).ScanAndCount(ctx, &users)
	if err != nil {
		return model.PageResult[model.DUser]{}, err
	}

	result := model.PageOf(param, users, count)
	return result, nil

}

// AddUser implements UserService.
func (s *userServiceImpl) AddUser(ctx context.Context, param model.UserPayload) error {

	return s.db.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
		var users []model.DUser
		err := tx.NewSelect().Model((*model.DUser)(nil)).Where("account = ?", param.Account).Limit(1).Scan(ctx, &users)
		if err != nil {
			return err
		}
		if len(users) > 0 {
			return errOf(400, fmt.Errorf("存在重复的账号: %s", param.Account))
		}

		user := model.DUser{
			Name:    param.Name,
			Account: param.Account,
		}
		_, err = tx.NewInsert().Model(&user).Exec(ctx)

		return err
	})

}

// ListUsers implements UserService.
func (s *userServiceImpl) ListUsers(ctx context.Context, name string) ([]model.DUser, error) {
	var users []model.DUser
	qb := s.db.NewSelect().Model((*model.DUser)(nil)).QueryBuilder()
	if name != "" {
		qb.Where("name = ?", name)
	}
	err := qb.Unwrap().(*bun.SelectQuery).Scan(ctx, &users)
	return users, err

}
